module Pan
  
  class Req
    attr_reader :params, :sid, :vid, :cb, :public_path
    attr_accessor :path

    def initialize(mongrel_request)
      @env = mongrel_request.params
      @path = @env['REQUEST_PATH']
      @new_path = [entry_point()]
      path_parts = path.split("/")
      if path_parts[-1].match /^([A-F0-9]{8,8})([A-F0-9]{4,4})([_A-F0-9]{0,5})$/
        @sid = $1
        @vid = $2
        if $3.size == 5
          @cb = $3[1..-1]
        else
          @cb = false
        end
        @public_path = path_parts[0..-2].join("/")
      else
        @public_path = path_parts[0..-1].join("/")
      end

      @method = @env['REQUEST_METHOD']
      if @method == 'GET'
        @params = Mongrel::HttpRequest.query_parse(@env['QUERY_STRING'])
      elsif @method == 'POST'
        if @env['HTTP_CONTENT_TYPE'].index("multipart/form-data") == 0
          ctype = @env['HTTP_CONTENT_TYPE']
          boundry = ctype.split(";").map{|ea| ea.strip}[1].split("=")[1]
          @params = parse_multipart(mongrel_request.body, boundry, @env)
        else
          @params = Mongrel::HttpRequest.query_parse(mongrel_request.body().string)
        end
      else
        raise Exception("I can only handle GET and POST requests, got: #{@method}")
      end
    end
    
    def callbacks
      @params.sort{|a,b| a.first <=> b.first} << [@cb, ""]
    end

    def normalize_params(parms, key, val)
      case key
      when /(.+)\[(.+)\]\[\]$/
        parms[$1] ||= {}
        parms[$1] = normalize_params(parms[$1], "#{$2}[]", val)
      when /(.+)\[(.+)\]$/
        parms[$1] ||= {}
        parms[$1] = normalize_params(parms[$1], $2, val)
      when /(.+)\[\]$/
        (parms[$1] ||= []) << val
      else
        parms[key] = val if val
      end
      parms
    end
    
    private
    
    def hack
      if i = path.index("__")
        sid, vid, cb_id, is_cb = path[i+2..-1].split("_")
        @params['sid'] = sid
        @params['vid'] = vid
        @params[cb_id] = ""
        @params['cb'] = "true"
        @path = path[0..i]
      end
    end
    
    public
    
    def add_to_path(str)
      @new_path << str
    end

    def finalize_new_path
      @path = @new_path.join("/")
    end
    
    def hostname
      @env['SERVER_NAME']
    end
    def port
      @env['SERVER_PORT'].to_i
    end
    def entry_point
      Dynamic.entry_point
    end
    def port_if_needed
      port == 80 ? "" : ":#{port}"
    end
    def server_url
      "http://#{hostname}#{port_if_needed}/"
    end
    def base_url
      "http://#{hostname}#{port_if_needed}#{path}"
    end
    def application_url
      "http://#{hostname}#{port_if_needed}#{entry_point}"
    end
    def public_url
      "http://#{hostname}#{port_if_needed}#{public_path}"
    end
    def to_s
      "#{@method}:#{path}:#{@params.inspect}"
    end

    private
  
    NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
    CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
    FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
    CRLF = "\r\n".freeze
    EOL = CRLF

    def parse_multipart(request,boundary,env)
      boundary = "--#{boundary}"
      paramhsh = {}
      buf = ""
      content_length = env['CONTENT_LENGTH'].to_i
      input = request
      input.binmode if defined? input.binmode
      boundary_size = boundary.size + EOL.size
      bufsize = 16384
      content_length -= boundary_size
      status = input.read(boundary_size)
      raise EOFError, "bad content body |#{status}|"  unless status == boundary + EOL
      rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
        loop {
        head = nil
        body = ''
        filename = content_type = name = nil
        read_size = 0
        until head && buf =~ rx
          i = buf.index("\r\n\r\n")
          if( i == nil && read_size == 0 && content_length == 0 )
            content_length = -1
            break
          end
          if !head && i
            head = buf.slice!(0, i+2) # First \r\n
            buf.slice!(0, 2)          # Second \r\n
            filename = head[FILENAME_REGEX, 1]
            content_type = head[CONTENT_TYPE_REGEX, 1]
            name = head[NAME_REGEX, 1]
           
            if filename && !filename.empty?    
              body = Tempfile.new(:Merb)
              body.binmode if defined? body.binmode
            end
            next
          end

          
          # Save the read body part.
          if head && (boundary_size+4 < buf.size)
            body << buf.slice!(0, buf.size - (boundary_size+4))
          end
      
          read_size = bufsize < content_length ? bufsize : content_length
          if( read_size > 0 )
            c = input.read(read_size)
            raise EOFError, "bad content body"  if c.nil? || c.empty?
            buf << c
            content_length -= c.size
          end
        end

        # Save the rest.
        if i = buf.index(rx)
          body << buf.slice!(0, i)
          buf.slice!(0, boundary_size+2)
      
          content_length = -1  if $1 == "--"
        end
        
        if filename && !filename.empty?   
          body.rewind
          data = { 
         	  :filename => File.basename(filename),  
         	  :content_type => content_type,  
         	  :tempfile => body, 
         	  :size => File.size(body) 
         	}
        else
          data = body
        end
        paramhsh = normalize_params(paramhsh,name,data)
        break  if buf.empty? || content_length == -1
      }
      paramhsh
    end
  end
end  
